package hawk.example.bookstore.repository.impl;

import static glz.hawkframework.support.ArgumentSupport.*;

import glz.hawkframework.dao.base.process.InsertProcessor;
import glz.hawkframework.dao.base.process.UpdateProcessor;
import glz.hawkframework.sql.condition.Condition;
import glz.hawkframework.sql.util.QueryWrapper;

import hawk.example.bookstore.mapper.AuthorMapper;
import hawk.example.bookstore.po.AuthorPo;
import hawk.example.bookstore.repository.AuthorRepository;
import hawk.example.bookstore.sql.provider.AuthorSqlProvider;
import hawk.example.bookstore.update.AuthorColumnUpdate;
import hawk.example.bookstore.update.AuthorUpdate;

import java.math.BigInteger;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
import javax.annotation.Nullable;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class AuthorRepositoryImpl implements AuthorRepository {
    private final AuthorMapper authorMapper;
    private final AuthorSqlProvider authorSqlProvider;
    @Autowired(required = false)
    private InsertProcessor insertProcessor;
    @Autowired(required = false)
    private UpdateProcessor updateProcessor;

    public AuthorRepositoryImpl(AuthorMapper authorMapper, AuthorSqlProvider authorSqlProvider) {
        this.authorMapper = argNotNull(authorMapper, "authorMapper");
        this.authorSqlProvider = argNotNull(authorSqlProvider, "authorSqlProvider");
    }

    @Override
    public void insert(AuthorPo authorPo) {
        argNotNull(authorPo, "authorPo");
        if (insertProcessor != null) {
            insertProcessor.beforeInsert(authorPo);
        }
        if (authorMapper.insert(authorSqlProvider.insert(authorPo)) != 1) {
            throw new IllegalStateException("Failed to insert authorPo.");
        }
        if (insertProcessor != null) {
            insertProcessor.afterInsert(authorPo);
        }
    }

    @Override
    public void insertSelective(AuthorPo authorPo) {
        argNotNull(authorPo, "authorPo");
        if (insertProcessor != null) {
            insertProcessor.beforeInsert(authorPo);
        }
        if (authorMapper.insertSelective(authorSqlProvider.insertSelective(authorPo)) != 1) {
            throw new IllegalStateException("Failed to insert authorPo.");
        }
        if (insertProcessor != null) {
            insertProcessor.afterInsert(authorPo);
        }
    }

    @Override
    public void insertMultiple(List<AuthorPo> authorPos) {
        final int defaultChunkSize = 200;
        insertMultiple(authorPos, defaultChunkSize);
    }

    @Override
    public void insertMultiple(List<AuthorPo> authorPos, int chunkSize) {
        argNotEmpty(authorPos, "authorPos");
        argument(chunkSize, c -> c > 0, c -> "The parameter['chunkSize'] must be greater than 0.");
        for (int i = 0; i < authorPos.size(); i += chunkSize) {
            List<AuthorPo> chunk = authorPos.subList(i, Math.min(i + chunkSize, authorPos.size()));
            if (insertProcessor != null) {
                insertProcessor.beforeInsert(chunk);
            }
            if (authorMapper.insertMultiple(authorSqlProvider.insertMultiple(chunk)) != chunk.size()) {
                throw new IllegalStateException("Failed to insert authorPos.");
            }
            if (insertProcessor != null) {
                insertProcessor.afterInsert(chunk);
            }
        }
    }

    @Override
    public void deleteByPrimaryKey(BigInteger authorId, Long recordVersion) {
        argNotNull(authorId, "authorId");
        argNotNull(recordVersion, "recordVersion");
        if (authorMapper.deleteGeneral(authorSqlProvider.deleteByPrimaryKey(authorId, recordVersion)) != 1) {
            throw new IllegalStateException("Failed to delete authorPo.");
        }
    }

    @Override
    public void updateByPrimaryKey(AuthorUpdate authorUpdate, BigInteger authorId, Long recordVersion) {
        argNotNull(authorUpdate, "authorUpdate");
        argNotNull(authorId, "authorId");
        argNotNull(recordVersion, "recordVersion");
        HashMap<String, Object> params = null;
        if (updateProcessor != null) {
            params = new HashMap<>();
            params.put(UpdateProcessor.UPDATE_BY_FIELD_OBJECT, authorUpdate);
            params.put(UpdateProcessor.RECORD_VERSION, recordVersion);
            params.put(UpdateProcessor.RECORD_ID, authorId);
            updateProcessor.beforeUpdate(params);
        }
        if (authorMapper.update(authorSqlProvider.updateByPrimaryKey(authorUpdate, authorId, recordVersion)) != 1) {
            throw new IllegalStateException("Failed to update authorPo.");
        }
        if (updateProcessor != null) {
            updateProcessor.afterUpdate(params);
        }
    }

    @Override
    public Optional<AuthorPo> getByPrimaryKey(BigInteger authorId) {
        argNotNull(authorId, "authorId");
        return authorMapper.selectOne(authorSqlProvider.selectByPrimaryKey(authorId));
    }

    /**
     * Throws an exception if found no required record by the primary key.
     */
    @Override
    public AuthorPo loadByPrimaryKey(BigInteger authorId) {
        argNotNull(authorId, "authorId");
        return authorMapper.selectOne(authorSqlProvider.selectByPrimaryKey(authorId)).orElseThrow(RuntimeException::new);
    }

    /**
     * Throws the supplied exception if found no required record by the primary key.
     */
    @Override
    public <E extends Throwable> AuthorPo loadByPrimaryKey(BigInteger authorId, Supplier<? extends E> exceptionSupplier) throws E {
        argNotNull(authorId, "authorId");
        argNotNull(exceptionSupplier, "exceptionSupplier");
        return authorMapper.selectOne(authorSqlProvider.selectByPrimaryKey(authorId)).orElseThrow(exceptionSupplier);
    }

    @Override
    public boolean existByPrimaryKey(BigInteger authorId) {
        argNotNull(authorId, "authorId");
        return authorMapper.count(authorSqlProvider.countByPrimaryKey(authorId)) == 1;
    }

    /**
     * Throws an exception if the required record doesn't exist
     */
    @Override
    public void assertExistByPrimaryKey(BigInteger authorId) {
        argNotNull(authorId, "authorId");
        if (authorMapper.count(authorSqlProvider.countByPrimaryKey(authorId)) != 1) {
            throw new RuntimeException("Record not exist");
        }
    }

    /**
     * Throws the supplied exception if the required record doesn't exist
     */
    @Override
    public <E extends Throwable> void assertExistByPrimaryKey(BigInteger authorId, Supplier<? extends E> exceptionSupplier) throws E {
        argNotNull(authorId, "authorId");
        argNotNull(exceptionSupplier, "exceptionSupplier");
        if (authorMapper.count(authorSqlProvider.countByPrimaryKey(authorId)) != 1) {
            throw exceptionSupplier.get();
        }
    }

    @Override
    public Optional<AuthorPo> queryOne(QueryWrapper queryWrapper) {
        argument(argNotNull(queryWrapper, "queryWrapper"), q -> !q.isCount(), q -> "The count field in the queryWrapper should be set to false.");
        return authorMapper.selectOne(authorSqlProvider.selectOrCountDynamic(queryWrapper));
    }

    @Override
    public List<AuthorPo> queryMany(QueryWrapper queryWrapper) {
        argument(argNotNull(queryWrapper, "queryWrapper"), q -> !q.isCount(), q -> "The count field in the queryWrapper should be set to false.");
        return authorMapper.selectMany(authorSqlProvider.selectOrCountDynamic(queryWrapper));
    }

    /**
     * Throws an exception if found no required record.
     */
    @Override
    public AuthorPo loadOne(QueryWrapper queryWrapper) {
        argument(argNotNull(queryWrapper, "queryWrapper"), q -> !q.isCount(), q -> "The count field in the queryWrapper should be set to false.");
        return authorMapper.selectOne(authorSqlProvider.selectOrCountDynamic(queryWrapper)).orElseThrow(RuntimeException::new);
    }

    /**
     * Throws the supplied exception if found no required record.
     */
    @Override
    public <E extends Throwable> AuthorPo loadOne(QueryWrapper queryWrapper, Supplier<? extends E> exceptionSupplier) throws E {
        argument(argNotNull(queryWrapper, "queryWrapper"), q -> !q.isCount(), q -> "The count field in the queryWrapper should be set to false.");
        argNotNull(exceptionSupplier, "exceptionSupplier");
        return authorMapper.selectOne(authorSqlProvider.selectOrCountDynamic(queryWrapper)).orElseThrow(exceptionSupplier);
    }

    @Override
    public long count(QueryWrapper queryWrapper) {
        argument(argNotNull(queryWrapper, "queryWrapper"), QueryWrapper::isCount, q -> "The count field in the queryWrapper should be set to true.");
        return authorMapper.count(authorSqlProvider.selectOrCountDynamic(queryWrapper));
    }

    @Override
    public boolean exist(QueryWrapper queryWrapper) {
        argument(argNotNull(queryWrapper, "queryWrapper"), QueryWrapper::isCount, q -> "The count field in the queryWrapper should be set to true.");
        return authorMapper.count(authorSqlProvider.selectOrCountDynamic(queryWrapper)) > 0;
    }

    /**
     * Throws an exception if found no required record.
     */
    @Override
    public void assertExist(QueryWrapper queryWrapper) {
        argument(argNotNull(queryWrapper, "queryWrapper"), QueryWrapper::isCount, q -> "The count field in the queryWrapper should be set to true.");
        if (authorMapper.count(authorSqlProvider.selectOrCountDynamic(queryWrapper)) <= 0) {
            throw new RuntimeException();
        }
    }

    /**
     * Throws the supplied exception if found no required record.
     */
    @Override
    public <E extends Throwable> void assertExist(QueryWrapper queryWrapper, Supplier<? extends E> exceptionSupplier) throws E {
        argument(argNotNull(queryWrapper, "queryWrapper"), QueryWrapper::isCount, q -> "The count field in the queryWrapper should be set to true.");
        argNotNull(exceptionSupplier, "exceptionSupplier");
        if (authorMapper.count(authorSqlProvider.selectOrCountDynamic(queryWrapper)) <= 0) {
            throw exceptionSupplier.get();
        }
    }

    @Override
    public boolean existOne(QueryWrapper queryWrapper) {
        argument(argNotNull(queryWrapper, "queryWrapper"), QueryWrapper::isCount, q -> "The count field in the queryWrapper should be set to true.");
        return authorMapper.count(authorSqlProvider.selectOrCountDynamic(queryWrapper)) == 1;
    }

    /**
     * Throws an exception if found no required record or found more than one record.
     */
    @Override
    public void assertExistOne(QueryWrapper queryWrapper) {
        argument(argNotNull(queryWrapper, "queryWrapper"), QueryWrapper::isCount, q -> "The count field in the queryWrapper should be set to true.");
        if (authorMapper.count(authorSqlProvider.selectOrCountDynamic(queryWrapper)) == 0) {
            throw new RuntimeException();
        }
        if (authorMapper.count(authorSqlProvider.selectOrCountDynamic(queryWrapper)) > 1) {
            throw new RuntimeException();
        }
    }

    /**
     * Throws the supplied exception if found no required record or found more than one record.
     */
    @Override
    public <E extends Throwable> void assertExistOne(QueryWrapper queryWrapper, Supplier<? extends E> exceptionSupplier) throws E {
        argument(argNotNull(queryWrapper, "queryWrapper"), QueryWrapper::isCount, q -> "The count field in the queryWrapper should be set to true.");
        argNotNull(exceptionSupplier, "exceptionSupplier");
        if (authorMapper.count(authorSqlProvider.selectOrCountDynamic(queryWrapper)) == 0) {
            throw exceptionSupplier.get();
        }
        if (authorMapper.count(authorSqlProvider.selectOrCountDynamic(queryWrapper)) > 1) {
            throw exceptionSupplier.get();
        }
    }

    @Override
    public long delete(@Nullable Condition condition) {
        return authorMapper.deleteGeneral(authorSqlProvider.deleteDynamic(condition));
    }

    @Override
    public long update(AuthorColumnUpdate authorColumnUpdate, @Nullable Condition condition) {
        argNotNull(authorColumnUpdate,"authorColumnUpdate");
        HashMap<String, Object> params = null;
        if (updateProcessor != null) {
            params = new HashMap<>();
            params.put(UpdateProcessor.UPDATE_BY_COLUMN_OBJECT, authorColumnUpdate);
            updateProcessor.beforeUpdate(params);
        }
        long affectedRowCount =  authorMapper.updateGeneral(authorSqlProvider.updateDynamic(authorColumnUpdate, condition));
        if (updateProcessor != null) {
            updateProcessor.afterUpdate(params);
        }
        return affectedRowCount;
    }
}
